package com.simen.hellobytes.socket;

import com.simen.hellobytes.StringUtil;
import com.simen.hellobytes.lang.TypeParseUtil;

import java.net.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

/**
 * 从{@link https://4sysops.com/archives/ipv6-tutorial-part-6-site-local-addresses-and-link-local-addresses/}了解site local与link local区分
 *
 * @author Created by Simen.
 * @date 创建日期 2018/4/13 18:11
 * @modify 修改者 Simen
 */
public class IpUtil {

    private static final String IPV4_BASIC_PATTERN_STRING =
            "(([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){1}" + // initial first field, 1-255
                    "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){2}" + // following 2 fields, 0-255 followed by .
                    "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"; // final field, 0-255

    private static final Pattern IPV4_PATTERN =
            Pattern.compile("^" + IPV4_BASIC_PATTERN_STRING + "$");

    private static final Pattern IPV4_MAPPED_IPV6_PATTERN = // TODO does not allow for redundant leading zeros
            Pattern.compile("^::[fF]{4}:" + IPV4_BASIC_PATTERN_STRING + "$");

    private static final Pattern IPV6_STD_PATTERN =
            Pattern.compile(
                    "^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}$");

    private static final Pattern IPV6_HEX_COMPRESSED_PATTERN =
            Pattern.compile(
                    "^(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)" + // 0-6 hex fields
                            "::" +
                            "(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)$"); // 0-6 hex fields

    /*
     *  The above pattern is not totally rigorous as it allows for more than 7 hex fields in total
     */
    private static final char COLON_CHAR = ':';

    // Must not have more than 7 colons (i.e. 8 fields)
    private static final int MAX_COLON_COUNT = 7;

    public static String getLocalIp() throws SocketException {
        return getLocalIp(null);
    }

    /**
     * @param filterIp 要匹配的ip地址（匹配规则，从前往后逐字符匹配）
     * @return 返回与 filterIp 最接近的本机ip地址
     */
    public static String getLocalIp(String filterIp) throws SocketException {
        String[] ipArray = getLocalIps();

        if (StringUtil.isTrimEmpty(filterIp)) {
            if (ipArray == null || ipArray.length == 0) {
                return null;
            }

            return ipArray[0];
        }

        if (ipArray != null) {
            String[] filters = filterIp.split("\\.");
            int maxSimilarIndex = 0;
            int lastSimilarity = 0;

            for (int m = 0; m < ipArray.length; m++) {
                String[] parts = ipArray[m].split("\\.");
                int similarity = 0;
                for (int n = 0; n < 4; n++) {
                    if (parts[n].equals(filters[n])) {
                        similarity++;
                    } else {
                        break;
                    }
                }

                if (similarity > lastSimilarity) {
                    maxSimilarIndex = m;
                    lastSimilarity = similarity;
                }
            }

            return ipArray[maxSimilarIndex];
        }

        return null;
    }

    public static String[] getLocalIps() throws SocketException {
        return getLocalIps(false);
    }


    public static String[] getLocalIps(boolean onlyIpv4) throws SocketException {
        return getLocalIps(onlyIpv4, true);
    }

    /**
     * 获取本机所有ip
     *
     * @param onlyIpv4              是否只搜索ipv4地址
     * @param onlyPrivateInternetIp 是否只搜索局域网地址.注意，ipv6地址没有公网私网区分，所以onlyPrivateInternetIp仅在onlyIpv4等于true时有效
     */
    public static String[] getLocalIps(boolean onlyIpv4, boolean onlyPrivateInternetIp) throws SocketException {
        List<String> ipList = new ArrayList<String>();

        Enumeration e = NetworkInterface.getNetworkInterfaces();
        while (e.hasMoreElements()) {
            NetworkInterface networkInterface = (NetworkInterface) e.nextElement();

            Enumeration ee = networkInterface.getInetAddresses();
            while (ee.hasMoreElements()) {
                InetAddress inetAddress = (InetAddress) ee.nextElement();
                String ipAddress = inetAddress.toString();

//                if (inetAddress.isSiteLocalAddress()
//                        && !inetAddress.isAnyLocalAddress()
//                        && !inetAddress.isLinkLocalAddress()
//                        && !inetAddress.isLoopbackAddress()
//                        && !inetAddress.isMulticastAddress()) {
                if (onlyIpv4) {
                    if (inetAddress instanceof Inet4Address) {
                        String ipv4Address = ipAddress.replace("/", "");
                        if (onlyPrivateInternetIp) {
                            if (isIPv4PrivateInternetAddress(ipv4Address)) ipList.add(ipv4Address);
                        } else {
                            ipList.add(ipv4Address);
                        }
                    }
                } else {
                    if (inetAddress instanceof Inet6Address) {
                        String ipv6Address = ipAddress.replace("/", "");
                        int percentSignIndex = ipv6Address.indexOf("%");
                        if (percentSignIndex == -1) {
                            ipList.add(ipv6Address);
                        } else {
                            ipList.add(ipv6Address.substring(0, percentSignIndex));
                        }
                    } else {
                        ipList.add(ipAddress.replace("/", ""));
                    }
                }
//                }
            }
        }

        if (ipList.isEmpty()) {
            return null;
        }

        String[] ipArray = new String[ipList.size()];
        ipList.toArray(ipArray);

        return ipArray;
    }

    /**
     * 获取本机所在网段所有的广播地址
     *
     * @return
     * @throws SocketException
     */
    @SuppressWarnings("Since15")
    public static String[] getBroadcastAddress() throws SocketException {
        List<String> Ips = new ArrayList<String>();

        Enumeration e = NetworkInterface.getNetworkInterfaces();
        while (e.hasMoreElements()) {
            NetworkInterface networkInterface = (NetworkInterface) e.nextElement();

            List<InterfaceAddress> interfaceAddresses = networkInterface.getInterfaceAddresses();
            if (interfaceAddresses != null) {
                Iterator<InterfaceAddress> iterator = interfaceAddresses.iterator();
                while (iterator.hasNext()) {
                    InterfaceAddress interfaceAddress = iterator.next();

                    if (interfaceAddress != null) {
                        InetAddress broadcast = interfaceAddress.getBroadcast();

                        if (broadcast instanceof Inet4Address) {
                            Ips.add(broadcast.toString().replace("/", ""));
                        }
                    }
                }
            }
        }

        if (Ips.isEmpty()) {
            return null;
        }

        String[] ipArray = new String[Ips.size()];
        Ips.toArray(ipArray);

        return ipArray;
    }

    /*** from ------ httpclient4.5.11 -------startHeadRequest ***/
    /**
     * Checks whether the parameter is a valid IPv4 address
     *
     * @param input the address string to check for validity
     * @return true if the input parameter is a valid IPv4 address
     */
    public static boolean isIPv4Address(final String input) {
        return IPV4_PATTERN.matcher(input).matches();
    }

    public static boolean isIPv4MappedIPv64Address(final String input) {
        return IPV4_MAPPED_IPV6_PATTERN.matcher(input).matches();
    }

    /**
     * Checks whether the parameter is a valid standard (non-compressed) IPv6 address
     *
     * @param input the address string to check for validity
     * @return true if the input parameter is a valid standard (non-compressed) IPv6 address
     */
    public static boolean isIPv6StdAddress(final String input) {
        return IPV6_STD_PATTERN.matcher(input).matches();
    }

    /**
     * Checks whether the parameter is a valid compressed IPv6 address
     *
     * @param input the address string to check for validity
     * @return true if the input parameter is a valid compressed IPv6 address
     */
    public static boolean isIPv6HexCompressedAddress(final String input) {
        int colonCount = 0;
        for (int i = 0; i < input.length(); i++) {
            if (input.charAt(i) == COLON_CHAR) {
                colonCount++;
            }
        }
        return colonCount <= MAX_COLON_COUNT && IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches();
    }

    /**
     * Checks whether the parameter is a valid IPv6 address (including compressed).
     *
     * @param input the address string to check for validity
     * @return true if the input parameter is a valid standard or compressed IPv6 address
     */
    public static boolean isIPv6Address(final String input) {
        return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input);
    }

    /*** from ------ httpclient4.5.11 -------end ***/

    /**
     * 局域网地址包含以下：
     * （10.0.0.0        -   10.255.255.255  (10/8 prefix)）
     * （172.16.0.0      -   172.31.255.255  (172.16/12 prefix)）
     * （192.168.0.0     -   192.168.255.255 (192.168/16 prefix)）
     */
    public static boolean isIPv4PrivateInternetAddress(String ipAddress) {
        if (isIPv4Address(ipAddress)) {
            String[] array = ipAddress.split("\\.");
            if ("10".equals(array[0])) {
                return true;
            } else if ("172".equals(array[0])) {
                int num = TypeParseUtil.parseInt(array[1], 0);
                if (num >= 16 && num <= 31) return true;
            } else if ("192".equals(array[0]) && "168".equals(array[1])) {
                return true;
            }
        }

        return false;
    }
}
